﻿using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using Geometry;
using Microsoft.Win32;
using GeoStructor;



namespace GeoStructor
{
    using Point = System.Windows.Point;
    using Vector = System.Windows.Vector;
    using Rect = System.Windows.Rect;
    using System.IO;
    using System.IO.Packaging;
    using System.Net.Mime;
    using System.Xaml;
    using System.Xml;

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>

    public partial class Window1 : System.Windows.Window
    {
        private GEngine Engine { get; set; }

        private Point m_MouseDownPoint;
        private IDraggable m_ItemDragSource;
        private int m_TempZIndex;
        private bool m_ItemDragged;
        private bool m_IsSynchronizingSelection = false;
        private TemplatedSelectionAdorner _dragSelection;

        public TemplatedSelectionAdorner DragSelection
        {
            get { return _dragSelection; }
            set { _dragSelection = value; }
        }

        public Window1()
        {
            // TODO: Temporary Solution. This prevents the localization of exception messages.
            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");

            InitializeComponent();
            this.Engine = new GEngine();
            this.DataContext = this.Engine;
        }

        public Point SelectionStartPoint
        {
            get { return (Point)GetValue(SelectionStartPointProperty); }
            set { SetValue(SelectionStartPointProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectionStartPoint.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectionStartPointProperty =
            DependencyProperty.Register("SelectionStartPoint", typeof(Point), typeof(Window1), new UIPropertyMetadata(default(Point), SelectionPointChanged));



        public Point SelectionFinishPoint
        {
            get { return (Point)GetValue(SelectionFinishPointProperty); }
            set { SetValue(SelectionFinishPointProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectionFinishPoint.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectionFinishPointProperty =
            DependencyProperty.Register("SelectionFinishPoint", typeof(Point), typeof(Window1), new UIPropertyMetadata(default(Point), SelectionPointChanged));


        private static void SelectionPointChanged(object sender, DependencyPropertyChangedEventArgs ea)
        {
            var instance = sender as Window1;

            var select = new List<IGObject>();
            Rect area = new Rect(instance.SelectionStartPoint, instance.SelectionFinishPoint);
            ScaleTransform st = new ScaleTransform(DrawingSettings.PanZoom.ScaleX, DrawingSettings.PanZoom.ScaleY);
            TranslateTransform tt = new TranslateTransform(
                DrawingSettings.PanZoom.TransformX + DrawingSettings.PanZoom.UserScrollX,
                DrawingSettings.PanZoom.TransformY + DrawingSettings.PanZoom.UserScrollY);
            MatrixTransform mt = new MatrixTransform(1, 0, 0, -1, 0, 0);
            area.Offset(-2, -2);
            area = tt.Inverse.TransformBounds(area);
            area = mt.TransformBounds(area);
            area = st.Inverse.TransformBounds(area);
            foreach (var item in instance.Engine.Graph)
            {
                if (item.BoundingBox.HasValue != true) continue;
                var bb = item.BoundingBox.Value;
                if (area.Contains(new Rect(bb.X, bb.Y, bb.Width, bb.Height)) != true) continue;
                select.Add(item);
            }
            instance.UpdateSelectionIfNecessary(select);
        }


        private void Drawings_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_ItemDragSource != null)
            {
                if (m_ItemDragSource.IsDragged) return;
            }

            if (m_IsSynchronizingSelection) return;
            m_IsSynchronizingSelection = true;

            this.xSelectionSettings.SelectedItems.Clear();
            var items = (sender as ListBox).SelectedItems;
            foreach (var item in items)
            {
                this.xSelectionSettings.SelectedItems.Add(item);
            }
            this.Engine.HandleSelection(items);

            m_IsSynchronizingSelection = false;
        }


        private void Settings_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_IsSynchronizingSelection) return;
            m_IsSynchronizingSelection = true;

            this.xDrawings.SelectedItems.Clear();
            var items = (sender as ListBox).SelectedItems;
            foreach (var item in items)
            {
                this.xDrawings.SelectedItems.Add(item);
            }
            this.Engine.HandleSelection(items);

            m_IsSynchronizingSelection = false;
        }

        private void CanExecuteClear(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.xDrawings.SelectedItems.Count > 0;
            e.Handled = true;
        }

        private void ExecutedClear(object sender, ExecutedRoutedEventArgs e)
        {
            this.xDrawings.SelectedItems.Clear();
            e.Handled = true;
        }

        private void CanExecuteDelete(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.xDrawings.SelectedItems.Count > 0;
            e.Handled = true;
        }

        private void ExecutedDelete(object sender, ExecutedRoutedEventArgs e)
        {
            var copy = this.xDrawings.SelectedItems.Cast<IGObject>().ToList();
            this.xDrawings.SelectedItems.Clear();
            if (!this.Engine.DeleteItems(copy))
            {
                copy.ForEach(i => { this.xDrawings.SelectedItems.Add(i); });
                MessageBox.Show(this,
                    "Some of the selected items are required by some items, outside the selection",
                    "Deletion failed!",
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
            }
            e.Handled = true;
        }

        private void ExecutedSaveGraph(object sender, ExecutedRoutedEventArgs e)
        {
            var sfd = new SaveFileDialog();
            sfd.Filter = "Geostructor graph file| *.xgml";
            if (sfd.ShowDialog() != true) return;
            this.Engine.SaveGraphFile(sfd.FileName);
            e.Handled = true;
        }

        private void ExecutedLoadGraph(object sender, ExecutedRoutedEventArgs e)
        {
            var ofd = new OpenFileDialog();
            ofd.Filter = "Geostructor graph file| *.xgml";
            ofd.CheckFileExists = true;
            if (ofd.ShowDialog() != true) return;
            this.Engine.LoadGraphFile(ofd.FileName);
            e.Handled = true;
        }

        private void ExecutedSaveHistory(object sender, ExecutedRoutedEventArgs e)
        {
            var sfd = new SaveFileDialog();
            sfd.Filter = "Geostructor history file| *.xgp";
            if (sfd.ShowDialog() != true) return;
            SaveHistory(sfd.FileName);
            e.Handled = true;
        }

        public void LoadHistory(string p)
        {
            var package = Package.Open(p, FileMode.Open);
            Engine.Snapshots.Clear();
            for (int i = 0; i < int.MaxValue; i++)
            {
                var rel = string.Format("/{0}/data.xgml", i);
                var uri = PackUriHelper.CreatePartUri(new Uri(rel, UriKind.Relative));
                if (!package.PartExists(uri)) break;
                
                var part = package.GetPart(uri);
                var ss = new GraphSnapshot();
                using (var reader = new StreamReader(part.GetStream()))
                {
                    ss.Data = reader.ReadToEnd();
                    reader.Close();
                }
                
                rel = string.Format("/{0}/meta.xaml", i);
                uri = PackUriHelper.CreatePartUri(new Uri(rel, UriKind.Relative));
                part = package.GetPart(uri);
                using (var reader = new StreamReader(part.GetStream()))
                {
                    ss.Meta = XamlServices.Load(reader) as SnapshotMetaData;
                    reader.Close();
                }
                Engine.Snapshots.Add(ss);
            }
            package.Close();
            var s = Engine.Snapshots.Count - 1;
            while (s > 0 && Engine.Snapshots[s].Meta.Undone)
            {
                s--;
            }
            Engine.LoadGraphData(Engine.Snapshots[s].Data);
        }

        public void SaveHistory(string p)
        {
            var package = Package.Open(p, FileMode.Create);
            for (int i = 0; i < Engine.Snapshots.Count; i++)
            {
                var ss = Engine.Snapshots[i];
                {
                    var rel = string.Format("/{0}/data.xgml", i);
                    var uri = new Uri(rel, UriKind.Relative);
                    var part = package.CreatePart(PackUriHelper.CreatePartUri(uri), MediaTypeNames.Text.Xml);
                    using (var writer = new StreamWriter(part.GetStream()))
                    {
                        writer.Write(ss.Data);
                        writer.Close();
                    }
                }
                {
                    var rel = string.Format("/{0}/meta.xaml", i);
                    var uri = new Uri(rel, UriKind.Relative);
                    var part = package.CreatePart(PackUriHelper.CreatePartUri(uri), MediaTypeNames.Text.Xml);
                    using (var writer = new StreamWriter(part.GetStream()))
                    {
                        XamlServices.Save(writer, ss.Meta);
                        writer.Close();
                    }
                }
            }
            package.Close();
        }

        private void ExecutedLoadHistory(object sender, ExecutedRoutedEventArgs e)
        {
            var ofd = new OpenFileDialog();
            ofd.Filter = "Geostructor history file| *.xgp";
            ofd.CheckFileExists = true;
            if (ofd.ShowDialog() != true) return;
            LoadHistory(ofd.FileName);
            e.Handled = true;
        }

        private void CanExecuteHistory(object sender, CanExecuteRoutedEventArgs e)
        {
            if (e.Parameter is string)
            {
                string param = e.Parameter as string;
                int move = 0;
                if (int.TryParse(param, out move))
                {
                    e.CanExecute = move > 0 ?
                        this.Engine.Snapshots.Any(gs => gs.Meta.Undone) :
                        this.Engine.Snapshots.Count(gs => !gs.Meta.Undone) > 1;
                }
            }
            else if (e.Parameter is GraphSnapshot)
            {
                e.CanExecute = this.Engine.Snapshots.Contains(e.Parameter as GraphSnapshot);
            }
            e.Handled = true;
        }


        private void ExecutedHistory(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Parameter is string)
            {
                string param = e.Parameter as string;
                int move = 0;
                if (int.TryParse(param, out move)) this.Engine.MoveInHistory(move);
            }
            else if (e.Parameter is GraphSnapshot)
            {
                this.Engine.MoveToSnapshot(e.Parameter as GraphSnapshot);
            }
            e.Handled = true;
        }

        private void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.Engine.Snapshots.Count(gs => !gs.Meta.Undone) > 1;
            e.Handled = true;
        }


        private void ExecutedUndo(object sender, ExecutedRoutedEventArgs e)
        {
            this.Engine.MoveInHistory(-1);
            e.Handled = true;
        }

        private void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.Engine.Snapshots.Any(gs => gs.Meta.Undone);
            e.Handled = true;
        }


        private void ExecutedRedo(object sender, ExecutedRoutedEventArgs e)
        {
            this.Engine.MoveInHistory(+1);
            e.Handled = true;
        }

        private void ExecutedShowDialog(object sender, ExecutedRoutedEventArgs e)
        {
            var type = e.Parameter as Type;
            var dialog = Activator.CreateInstance(type) as Window;
            dialog.Owner = this;
            dialog.ShowDialog();
            e.Handled = true;
        }

        private void Drawings_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            DrawingSettings.PanZoom.TransformX = e.NewSize.Width / 2;
            DrawingSettings.PanZoom.TransformY = e.NewSize.Height / 2;
        }

        private void Drawings_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.MiddleButton == MouseButtonState.Pressed)
            {
                m_MouseDownPoint = e.GetPosition(this.xDrawings);
                this.xDrawings.Cursor = Cursors.ScrollAll;
            }
        }

        private void ExecutedGenerate(object sender, ExecutedRoutedEventArgs e)
        {
            var gen = e.Parameter as GObjectGenerator;
            var newGO = gen.Generate();
            this.Engine.Graph.Add(newGO);
            this.xDrawings.SelectedItem = newGO;
            this.Engine.CreateSnapshot("Added " + newGO.Name);
            e.Handled = true;
        }



        private void ExecutedZoomBy(object sender, ExecutedRoutedEventArgs e)
        {
            ZoomDrawingsBy((double)(e.Parameter));
        }

        private void Drawings_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            ZoomDrawingsBy(e.Delta < 0 ? 1.25 : 0.8);
        }

        private void ZoomDrawingsBy(double factor)
        {
            DrawingSettings.PanZoom.ScaleX *= factor;
            DrawingSettings.PanZoom.ScaleY *= factor;
            DrawingSettings.PanZoom.UserScrollX *= factor;
            DrawingSettings.PanZoom.UserScrollY *= factor;
        }

        private void Drawings_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.MiddleButton != MouseButtonState.Pressed) return;

            Point p = e.GetPosition(this.xDrawings);
            Vector m = p - m_MouseDownPoint;
            DrawingSettings.PanZoom.UserScrollX += m.X;
            DrawingSettings.PanZoom.UserScrollY += m.Y;
            m_MouseDownPoint = p;

            e.Handled = true;
        }

        private void Drawings_MouseUp(object sender, MouseButtonEventArgs e)
        {
            this.xDrawings.Cursor = Cursors.Arrow;
        }

        private void Drawings_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton != MouseButtonState.Pressed) return;
            Point pos = e.GetPosition(this.xDrawings);
            Vector move = pos - m_MouseDownPoint;
            if (move.LengthSquared == 0) return;
            if (m_ItemDragSource != null)
            {
                e.Handled = true;
                if (move.LengthSquared == 0) return;
                move.X = move.X / DrawingSettings.PanZoom.ScaleX;
                // mouse coords are upside down
                move.Y = -move.Y / DrawingSettings.PanZoom.ScaleY;
                m_ItemDragSource.DragBy(new Geometry.Vector(move.X, move.Y));
                m_MouseDownPoint = pos;
                m_ItemDragged = true;
            }
            else
            {
                if (DragSelection == null)
                {
                    SelectionStartPoint = pos;
                    SelectionFinishPoint = pos;
                    // selection
                    DragSelection = new TemplatedSelectionAdorner(this.xDrawings);
                    DragSelection.SetBinding(TemplatedSelectionAdorner.StartProperty, new Binding()
                    {
                        Source = this,
                        Path = new PropertyPath("SelectionStartPoint"),
                        Mode = BindingMode.TwoWay
                    });
                    DragSelection.SetBinding(TemplatedSelectionAdorner.FinishProperty, new Binding()
                    {
                        Source = this,
                        Path = new PropertyPath("SelectionFinishPoint"),
                        Mode = BindingMode.TwoWay
                    });
                    AdornerLayer.GetAdornerLayer(this.xDrawings).Add(DragSelection);
                    DragSelection.CaptureMouse();
                }
                else
                {
                    //SelectionFinishPoint = pos;
                }
            }

        }

        private void UpdateSelectionIfNecessary(List<IGObject> select)
        {
            if (this.xDrawings.SelectedItems.Count != select.Count ||
                select.Any(go => !this.xDrawings.SelectedItems.Contains(go)))
            {
                this.xDrawings.SelectedItems.Clear();
                select.ForEach(go => this.xDrawings.SelectedItems.Add(go));
            }
        }

        private void Drawings_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (DragSelection != null)
            {
                AdornerLayer.GetAdornerLayer(this.xDrawings).Remove(DragSelection);
                DragSelection.InvalidateVisual();
                DragSelection = null;
            }
            if (e.ChangedButton != MouseButton.Left) return;
            m_MouseDownPoint = e.GetPosition(this.xDrawings);
            VisualTreeHelper.HitTest(this.xDrawings, FilterDraggable, RecordDraggable, new PointHitTestParameters(m_MouseDownPoint));
            if (m_ItemDragSource != null)
            {
                this.xDrawings.Cursor = Cursors.Hand;
                if (m_ItemDragSource is IGObject)
                {
                    foreach (var go in Engine.Graph)
                    {
                        if (go != m_ItemDragSource) go.IsEnabled = false;
                    }
                }
                var lbi = this.xDrawings.ItemContainerGenerator.ContainerFromItem(m_ItemDragSource) as ListBoxItem;  
                if (lbi != null)
                {
                    m_TempZIndex = Panel.GetZIndex(lbi);
                    Panel.SetZIndex(lbi, int.MaxValue);
                }
                m_ItemDragged = false;
            }

        }

        private HitTestFilterBehavior FilterDraggable(DependencyObject hit)
        {
            if (hit.GetValue(Draggable.SourceProperty) /*as IDraggable */!= null)
            {
                return HitTestFilterBehavior.Continue;
            }
            else
            {
                return HitTestFilterBehavior.ContinueSkipSelf;
            }
        }

        private HitTestResultBehavior RecordDraggable(HitTestResult htr)
        {
            m_ItemDragSource = htr.VisualHit.GetValue(Draggable.SourceProperty) as IDraggable;
            m_ItemDragSource.BeginDrag();

            return HitTestResultBehavior.Stop;
        }

        private void Drawings_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton != MouseButton.Left) return;

            if (m_ItemDragSource != null)
            {
                foreach (var go in Engine.Graph)
                {
                    go.IsEnabled = true;
                }
                m_ItemDragSource.EndDrag();
                if (m_ItemDragged)
                {
                    if (m_ItemDragSource is IGObject) this.Engine.CreateSnapshot("Item " + (m_ItemDragSource as IGObject).Name + " moved");
                    else this.Engine.CreateSnapshot("Item moved.");
                    this.xSnapshotScroller.ScrollToBottom();
                }
                var lbi = this.xDrawings.ItemContainerGenerator.ContainerFromItem(m_ItemDragSource) as ListBoxItem;
                if (lbi != null)
                {
                    Panel.SetZIndex(lbi, m_TempZIndex);
                }
                m_ItemDragSource = null;
                this.xDrawings.Cursor = Cursors.Arrow;
            }
            else if (DragSelection != null)
            {
                DragSelection.IsHitTestVisible = true;
            }

        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Workaround for a bug, that puts toolbar items into overflow mode until a resize occurs
            this.xGeneratorToolBar.InvalidateMeasure();
        }

        private void ExecutedExportImage(object sender, ExecutedRoutedEventArgs e)
        {
            var sfd = new SaveFileDialog
            {
                DefaultExt = "png",
                OverwritePrompt = true,
                AddExtension = true,
                Filter = "Portable Network Graphics|*.png",
            };
            if (sfd.ShowDialog() != true) return;

            var drawings = xDrawings.PointToScreen(new Point(0, 0));
            var main = xMainWindow.PointToScreen(new Point(0, 0));
            var offset = drawings - main;
            var bmp = new RenderTargetBitmap((int)(xDrawings.ActualWidth + offset.X), (int)(xDrawings.ActualHeight + offset.Y), 96, 96, PixelFormats.Pbgra32);
            bmp.Render(xDrawings);
            var png = new PngBitmapEncoder();

            if (DragSelection != null && (DragSelection.Finish - DragSelection.Start).LengthSquared > 0)
            {
                var rect = new Rect(DragSelection.Start + offset, DragSelection.Finish + offset);
                var crop = new Int32Rect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
                var cropped = new CroppedBitmap(bmp, crop);
                png.Frames.Add(BitmapFrame.Create(cropped));
            }
            else
            {
                var crop = new Int32Rect((int)offset.X, (int)offset.Y, (int)xDrawings.ActualWidth, (int)xDrawings.ActualHeight);
                var cropped = new CroppedBitmap(bmp, crop);
                png.Frames.Add(BitmapFrame.Create(cropped));
                png.Frames.Add(BitmapFrame.Create(bmp));
            }
            using (var stream = File.Create(sfd.FileName))
            {
                png.Save(stream);
                stream.Close();
            }
        }


    }

}
